import numpy as np

from pynumpak.type import func_t, Interval


def bisect(func: func_t, ab: Interval, max_iter: int = 100, x_tol: float = 2 ** (-51),
           f_tol: float = 1e-10, show_iter_time: bool = False):
    a, b = ab
    if a > b:
        a, b = b, a
    for i in range(max_iter):
        a, b = bisect_impl(func, a, b)
        if abs(b - a) < x_tol:
            return ((a + b) / 2, i + 1) if show_iter_time else (a + b) / 2
    else:
        if abs(func(a)) < f_tol or abs(func(b)) < f_tol:
            if show_iter_time:
                return (a, i + 1) if abs(func(a)) < abs(func(b)) else (b, i + 1)
            else:
                return a if abs(func(a)) < abs(func(b)) else b
        else:
            raise (ValueError("Unable to find root, please change another interval"))


def bisect_impl(func: func_t, a: float, b: float):
    def sgn(x):
        if func(x) == 0:
            return 0
        return 1 if func(x) > 0 else -1

    if sgn(a) == 0:
        return a, a
    if sgn(b) == 0:
        return b, b
    if sgn((a + b) / 2) == 0:
        return (a + b) / 2, (a + b) / 2
    if sgn(a) * sgn(b) == -1:
        if sgn((a + b) / 2) * sgn(a) == -1:
            return a, (a + b) / 2
        else:
            return (a + b) / 2, b

    x0 = np.linspace(a, b, 100)
    y0 = np.empty(shape=100, dtype=int)
    y0[0] = sgn(x0[0])
    for i in range(1, 100):
        y0[i] = sgn(x0[i])
        if y0[i] == 0:
            return x0[i], x0[i]
        if y0[i] * y0[i - 1] == -1:
            return x0[i - 1], x0[i]
    else:
        raise (ValueError("Unable to find root, please change another interval"))
